Hey there.
Welcome back.
In this lecture we're going to be learning about Parallel Lewis, which is a feature in Roblox Studio
that allows us to execute Lua code on multiple threads.
This is not the same as a coroutine thread, because coroutine threads are executed in serial and not
in parallel.
Let's first get a definition of what parallel and serial execution is.
Serial execution is what you are used to when programming in studio.
Serial means that code is executed in a sequence line by line, one after the other.
Serial means that the code has to wait for previous code to execute before moving on to the next set
of code.
Parallel execution, on the other hand, allows us to execute code simultaneously on multiple processor
threads.
This means we do not have to wait for a previous task to finish executing before starting the next task.
Parallel execution is great for when we need to do a bunch of calculations, and we can spread the work
of those calculations across multiple processor threads, which can help improve the frame time of our
game.
We can observe the frame time of our game by opening a tool called the Micro Profiler, which allows
us to observe each frame that is rendered and what exactly is being rendered or executed in each frame.
Now, this tells us we can open the Micro Profiler using Ctrl, alt F6 in studio or on the client.
When we're playing in the Roblox player and when open, a menu bar is visible at the top of the game
view.
Under it, there is a moving bar graph which reflects the time used on each frame of the task scheduler.
As they pass, the most recent frames appear on the right and flow to the left.
So here inside of studio, we can go ahead and open up the Micro Profiler by pressing Ctrl, alt and
F6.
And there you can see our beautiful Micro Profiler as it is recording all of the frames that are currently
being rendered.
So each one of these individual orange bars represents a frame, and the height of the bar represents
how long it took for that particular frame to completely execute.
Now we are able to pause and observe these frames by pressing control and p.
And now this screen should pop up and you will notice that we are no longer recording frames.
Now we can move our mouse around and look at each one of these frames, and it tells us specifically
what frame it was like.
This is frame 439.
This is frame 440.
And it tells us the CPU time for each one of these frames.
And this little green area tells us where we are currently observing in this panel right here.
So this panel is going to show us what exactly was executed in a particular frame.
And we can use our scroll wheel to zoom out.
So let's go ahead and start zooming out.
And you should see at the top as well that our little green view area has increased because now we're
looking at multiple different frames.
So each one of these little vertical bars represents the start and end of a frame.
So this is a frame.
This is a frame.
And then all of this other stuff is tasks that are being executed in every single frame.
You'll also notice on the left hand side each of these different labels like one's called GPU.
One is called RGB worker, RGB worker.
And then we keep going down.
There's more RGB workers.
And then there's another thread here called main slash render and then some other miscellaneous ones.
So each one of these labels represents a thread, a thread on your processor.
The number of threads being displayed here is going to depend on the processor inside of your computer.
Most processors nowadays are quad cores or hexacore with multithreading, meaning every single core
in the processor gets two threads.
If you have a quad core with Hyperthreading, that means you're going to have a total of eight threads,
while if you have a hexa core, you're going to have 12 threads inside of the Roblox documentation.
It tells us what each of these labels for the threads mean.
For example, the main process is input humanoids, animation, physics, sounds, update studio interfaces,
blah blah blah.
So this works on basically all the main stuff.
And then these other ones labeled worker helps our main thread with other tasks that it needs to calculate.
And then we also have a section for rendering such as our GPU for preparing.
It says information from the main thread is used to update rendering models, perform issue rendering
commands including 2D interfaces, and then present synchronizes with the graphics card.
This is some internal stuff we don't need to worry about now.
Any time we have a script executing in our game, such as a local script on the client.
Depending on how long it took for a local script to execute, it will appear inside of our micro profiler
depending on what it's doing.
So for example, if you have any scripts that are executing or listening to events after, let's say,
the heartbeat, then it's going to show up in a section called a deferred threads if you have deferred
signaling enabled.
So for example, if we take a look through here, let's go ahead and see if we can find the different
run service events.
So in the main slash render section there should be an area specifically for the run service render
stepped event.
So let's see if we can find it okay I see a render render step in.
Colonel.
Here we go.
Run service!
Render stepped.
And this tells us all the bunch of other stuff that is executing underneath the render stepped.
And then there's going to be other stuff executing.
If we connect any functions to the render step event.
And then there's also going to be the stepped event and the heartbeat.
We can see the heartbeat up here being executed on RHB worker 13.
But any time you connect a function to the render stepped event, it's going to appear under the main
slash render thread.
So as an example, I have a local script and replicated first that is called my local script one.
There's nothing in either of these local scripts, but in my first one we can go ahead and do is grab
the run service.
And let's go ahead and connect to the render stepped event.
So render stepped connect a function.
And then to be able to see this inside of our micro profiler I want this function to do a bunch of garbage.
That way it kind of extends the frame time or how long it takes to execute.
So what we can go ahead and do is create a for loop and have it iterate, let's say 100,000 times.
And we'll do something random in here, like let's do a math calculation.
We'll create a variable called a and we'll just set it equal to let's say the square root of I times
I just a simple basic stupid math calculation.
That way we're wasting resources for the processor and hopefully it'll appear inside of the micro profiler.
So if we play test our game and wait for our player to spawn in, and then let's go ahead and unpause
the micro profiler by pressing control and P, let's record some frames and then let's pause it again.
And then we can select one of these frames.
And let me go ahead and zoom out because I think we're zoomed in way too much.
And then let's go ahead and move down to the main slash render.
And let's find render stepped in here.
Okay.
Perfect.
Here is a run service render stepped.
And here is our local script appearing under our render stepped.
So it took our local script.
That function connected to it about 0.6 milliseconds to execute 100,000 iterations through that loop.
And it's doing that every single frame.
So if we move on to the next frame, let me go ahead and zoom out.
Let's go over to the next frame.
Here we can also see our local script.
Now currently in my workspace, if we go to the signal behavior it's set to immediate.
If we set this to deferred mode instead of the local script appearing underneath the run service render
step event, it's going to appear in an area after the event called Deferred Threads.
So let me go ahead and stop playtesting and let's swap signal behavior back to deferred.
Let's unpause the Micropro filer by pressing control and P record some frames, and then we'll pause
it again.
And let's go ahead and take a look at the main slash render thread.
And now we have this section appearing called deferred threads.
Let's see if we can find render stepped okay.
Perfect.
Render stepped is over here.
And then afterwards any functions connected to the render stepped event get put in that queue.
If you watched my video about deferred events so it's placed in this deferred threads queue.
And sometime later there we go.
Here is our local script number one taking 0.6 milliseconds to execute.
And it's doing that every single frame.
Now what if we want to be able to add a label to this local script, for example?
All of these other stuff have cool labels like this one says Update Light Grid and this one says shadows,
and this one says Update Perform.
How do we add a label to our local script?
Well, it's easy to do and we can do it using the debug library.
So inside of our local script we can go ahead and type out debug.
And it says this library provides functions useful for debugging and profiling code.
And inside of the debug library there is a function called profile.
Begin starts profiling for a micro profiler label.
And we can pass a label here.
So we'll give this one a name or label of render stepped work.
And then to close out this profile we need to call another function called profile end.
So any code that appears between these two function calls is going to appear inside of our micro profiler
with this particular label.
Let's go ahead and unpause.
And then repose.
And let's go down to the main thread.
And there we go.
Local script one.
Here is our label of render stepped work.
So this easily allows you to be able to track any kind of processes that you have going on in your local
scripts.
So for example, if you rely heavily on a bunch of visual effects on the client side that need to be
updated every single frame, then it might be a good idea to place it between a profile.
So that way down the line, if you're having any performance issues, you can go ahead and check to
see if there is something taking a long time to execute.
For example, this one's taking 0.8 milliseconds to execute every single frame.
Let's go ahead and continue filling up our micro profiler with more labels.
So in the run service let's connect to the stepped event.
And let's do the exact same thing that we're doing in the render step event.
So I'm just going to copy this and paste that there.
But this time we're going to call this our stepped work.
And then let's do the same thing for the heartbeat event.
We'll connect a function and do the exact same thing.
But this time we're going to call this our heartbeat work.
So now we should have three different labels appearing in the Micro Profiler for render stepped work,
stepped work, and heartbeat work.
Let's go ahead and pause our micro profiler.
And let's go ahead and see if we can find those different things.
And I can already see one right here.
Here we have our stepped work which is being handled by our worker nine and here's our local script
one performing its stepped work underneath the deferred threads.
Let's see if we can find heartbeat.
Oh look, just a little bit later here is the heartbeat event which executes after the stepped event.
And there's local script one performing heartbeat work.
And each time these are taken about 0.6 milliseconds to execute.
And then we can also go back down to the main render thread.
Let's go ahead and find.
There we go render stepped work for local script one.
Now you should also be noticing how this executes one after the other.
So first we execute our render step work.
Then we execute our stepped work and then we execute our heartbeat work.
Now to further demonstrate that Lua code executes sequentially or in serial, let's copy this and let's
connect two more functions to our heartbeat event.
But this time we're going to call this heartbeat work one.
This one is going to be heartbeat work two.
And this one's going to be heartbeat work three.
Let's go ahead and pause the micro profiler and take a look inside of the deferred thread section.
Here we have our local script executing each one of those heartbeat works.
So heartbeat work three heartbeat work two, heartbeat work one.
And it was handling it on our worker thread number zero.
And then if we go ahead and zoom out and let's move over to the next frame, let's see if we can find
where it's being executed.
So this time it's being handled by our worker six here is heartbeat work 3 to 1 as well as our render
step work happening down there.
And our step work.
Let's move to the next frame.
It's also again being handled by our worker six there's our heartbeat work.
Here's our step work and here's our render step work.
Again, if you notice each one of these functions is being executed one after the other.
So the first one executes, then the next one, then the next one.
To make this even more prominent, let's go ahead and copy all of the code we have in our first local
script.
So let's just copy this and let's paste it in our second local script as well.
So now we have two scripts doing the exact same thing connecting to the run service.
If we go ahead and pause our micro profiler and let's go ahead and take a look.
What do you notice?
We have Local Script two executing each one of its heartbeat work.
Then local script one also executing each one of its heartbeat work.
And as you can see, it's one after the other, one after the other.
Same thing with the step work.
We have local script two and local Script one executing their step work.
And same goes for render step work one after the other.
Now you might start to see the problem we're going to encounter with this.
And that is what if we have multiple of these local scripts executing at the same time, connected to
all these heartbeat events and render step events?
Well, eventually the size of this is going to be pushed out so far that it's going to start extending
the frame times.
So we're averaging currently 16 milliseconds of frame time.
Well, if we have more and more and more stuff executing sequentially, the frame time is going to increase
and it's going to cause a worse performance for our game.
To demonstrate this, let's go ahead and make these four loops even more expensive to execute.
Let's just add an extra zero and make this loop 1 million times for every single one.
And then let's copy that and do the exact same thing for Local Script two as well.
We'll just delete that and then paste the new code in there.
So now we have these four loops executing a million times.
Let's go ahead and see what happens to our performance.
As you can see our frame time is now horrible.
Look at our frame time average.
We're averaging 67 milliseconds of frame time and the game is lagging like crazy.
Let's go ahead and pause and then let's stop testing.
So we stop lagging.
And let's go ahead and take a look at these frames.
And if you notice, look at how long it is taking to execute each one of these works sequentially,
one after the other.
It's completely pushing the frame time of our frames.
This is 60 milliseconds from here to here and it's just awful performance.
So you may be thinking, good heavens, how do we solve this performance conundrum?
How can we spread out the work of each one of these heartbeat works?
And these render stepped works into multiple different threads?
Because look at RRB worker six.
Here he is struggling to execute all of this stuff by himself, while all of these other worker threads
are doing nothing at all.
How can we spread the work across all of these different threads?
Well, drum roll please.
This is where parallel luoyue can come into play.
So on the parallel Luoyue documentation page, it tells us with parallel luoyue you can run code on
multiple threads simultaneously, which can improve the performance of your experience.
As you expand your experience with more content, you can adopt this model to help maintain the performance
and safety of your Luoyue scripts.
And they give us an example down here of a terrain generation system.
While executing serially, you can see that it's having 60 millisecond frame times in its lagging the
game, but if you script it in a way where it is executing on multiple different threads, now the game
is performing well with a 17 millisecond frame time.
So they tell us by default scripts execute sequentially, which is what we just saw.
Code executes one after the other.
If your experience has complex logic or content such as non-player characters, raycasting validation,
and procedural generation, then sequential execution might cause lag for your users, which we just
observed.
With the parallel programming model, you can split tasks into multiple scripts and run them in parallel.
This makes your experience code run faster, which improves the user experience, and it tells us to
split code into multiple threads.
We have to put our scripts in actors called an actor instance.
If we look at the documentation page for what an actor is, it tells us that an actor is a container
for code that can be safely split into its own thread.
So those different worker threads using the task dot d synchronize function.
It should also contain the instances used by its scripts.
So back in studio, let's go ahead and create a new actor instance inside of replicated first so we
can just search for actor.
Here is our actor instance and we need to make our scripts a child of this actor.
So let's go ahead and put our scripts as a child of this actor.
And you might be thinking, great, we're done.
No more performance problems, right?
Well, you wish it was that easy because if we go and playtest our game, as you can see, we're still
running into performance problems.
And if we take a look at the micro profiler and we pause it, our code is still being executed sequentially
one after the other, causing super high frame times.
The reason for this is because we are not executing this code in parallel.
In order to execute this code in parallel, we should replace this connect function here with the connect
parallel function, which allows us to immediately run the code inside of here in parallel.
So let me go ahead and hold down alt and then click to add multiple of these cursors.
And we're going to change all of these to connect parallel.
And then let's go ahead and copy that and do that for Local Script two as well.
And the Roblox documentation tells us that instead of having to use the task dot d synchronize function,
we can use the script signal connect parallel method.
When you want to schedule a signal callback to immediately run your code in parallel upon triggering.
So that's what we just did.
We swapped all of the connect functions for connect parallel to start executing the code in parallel.
So now you may be thinking great, we're done.
Right?
Well let's go play test our game and see what happens.
And uh oh our game is still lagging.
What's going on?
If we check the micro profiler.
Whoa.
The frame times, they're still out of control.
Hold on.
Let's pause this and let's stop the test.
What is this?
Our code is still executing one after the other.
There is no code being split up across multiple different threads.
What is happening?
Well, we actually need to do one more thing.
In fact, what we need to do is we need to split our local scripts or separate them into multiple different
actors instead of having both scripts.
As a child of one actor, let's split them up into.
Two actors to execute on separate threads, because right now what's happening is the code in both of
these local scripts are going to still be executing on the same thread because they're in the same actor.
So let's add another actor into our game and let's move local script two into that second actor.
Now we should have the code and local script one execute in a different thread compared to the code
in our local script.
Number two now we are still lagging a bit, but if we go ahead and open up the micro profiler, what
you will notice is that our frame times have been cut in half.
So before we were averaging 60 to 70 milliseconds of frame time, and now we are averaging around 30
to 40 milliseconds of frame time.
So it was completely cut in half.
Now the reason why we're still lagging is because the code executing in these local scripts are very
stupid, and it's just wasting a whole bunch of resources.
But if we take a look here, what is happening, our code is being split up into multiple different
threads.
So here you can see that local script one is executing an RX worker 13, while our other local script
is executing an RX worker 14 and as we move along different frames, you can see how each of our tasks
are being split up into different threads.
And that's because we have two actors.
And since we have two actors, we can split the code into two different threads.
Now, let's be honest, you're probably never going to have this garbage work going on here where we're
iterating through a loop 1 million times, doing who knows what.
But let's go ahead and take a look at another example of how parallel Lua can improve the performance
of our game.
So let me go ahead and disable both of these local scripts so they're not impacting our performance
anymore.
And let's move on to the next example I would like to show you.
So in this example here I have two deer.
One of my deers is called serial deer.
And my other deer is called parallel deer.
The only difference between these two deer is that one of them has the script inside of an actor, while
our serial deer just has the regular script.
And the purpose of this script is just to move our deer to follow the closest player.
So we're going to wait for a player to join the game and get their character.
And then every single heartbeat, we're just moving, um, the deer towards the player's character.
And inside of here, we're also calculating some random garbage right here, just to make sure that
the frame time gets expanded for this particular heartbeat event.
So we're doing some random garbage here.
Let's say this is random calculations for an AI.
And then we're calculating to see if we're too far away from the player.
If we are, we'll just teleport to the player.
Otherwise we'll have our humanoid move towards that player.
And then we have a profile beginning and ending for our serial deer, and then for the script inside
of our parallel deer, we're doing the exact same thing, but this time we're doing it in parallel.
So we're still doing that random garbage.
But this time, before we set the keyframe property for our deer, or before we have the humanoid move
towards the player's character, we are calling the task Dot synchronize function, and the reason that
we need to call the task dot synchronize function is to execute the code afterwards in serial.
And that's because most functions and property inside of the Roblox API are not thread safe.
What do I mean by thread safe?
Well, for example, let's say two threads are attempting to write a property at the exact same time.
Or let's say one thread is writing to a property while another thread is reading that property at the
same time you are going to run into major problems.
So to fix that issue, Roblox has made many properties and many functions in their API either read safe
or write safe, or you can read and write to them, or you can't do any of it.
And we can check by going to the Roblox documentation and looking at each one of the properties or functions
for a particular thing in the Roblox API, and checking to see if it is safe to use in parallel.
So inside of the Roblox documentation for parallel luoyue, there's a section called Thread Safety,
and it says during the parallel execution, you can access most instances of the data model hierarchy
as usual, but some API properties and functions aren't safe to read or write.
If you use them in your parallel code, the Roblox engine can automatically detect and prevent these
accesses from occurring.
API members have a thread safety level that indicates whether and how you can use them in your parallel
code, as the following table shows.
So stuff in the Roblox API is going to be tagged with a particular safety level, determining if we
are able to read or write to it.
So for functions, if it is unsafe, we cannot call the function in parallel.
But if it is labeled as safe, then we can go ahead and call the function in parallel.
As an example, let's go ahead and look up the world route, which stores all of the functions for things
like spatial queries and raycasting.
And if we go ahead and take a look at one of the functions, for example, Raycasting, it tells us
that it is read and write.
Safe.
So it says you can read and write to this property safely from multiple threads, meaning we can call
this function while we are executing code in parallel.
It is read and write safe.
Now let's say we're looking through the base part class, and we want to figure out if we can set the
anchored property while we are executing code in parallel.
Well, if we head over to the anchor property, what does it tell us?
Oh, it tells us this property is read only and it is safe to read in unsynchronized threads, which
is parallel.
But attempting to write to this property while we are executing in parallel will cause an error.
So this is only read safe for the anchor property.
And this is going to be true for almost every single property.
For instances in your game, you'll be able to read them, but you will not be able to write to them.
This is why we are forced to synchronize and go back into serial execution in order to, for example,
set the C frame of a part or call the move to function on a humanoid.
We can only do so while we are executing code in serial.
Now what I'm going to do is I'm going to take my serial dear.
I'm going to make sure to enable this script inside of him, and let me go ahead and move him over here
and let's just duplicate him a bunch.
So we'll make two of them and then we'll make four.
So we'll have six here.
Then we'll make 12 of them and let's keep duplicating him.
So now we have a total of 24.
Now we'll duplicate him again.
Now we have 48.
And let's duplicate him one more time.
Now we have 72 dear.
All executing their move.
Two scripts, trying to do this random garbage and moving to the closest player's character.
So if we go and playtest the game, what you're going to notice here is that the game is lagging like
crazy.
I have all these deer following me, and if we open up the micro profiler, the current frame time is
averaging about 50 milliseconds.
If we go ahead and pause and then let's go ahead and stop.
And let's go ahead and take a look at one of these frames.
Like here's this frame right here.
Well what you're going to notice is that every single deer here it is a deer serial deer serial.
They're all executing one after the other, one after the other.
And this entire thread right here, RRB worker A is being completely overloaded, having to execute
every single deer.
And that's a problem because it's ruining the frame, time and performance of our game.
So how can we split up each one of these tasks?
So every single thread in our game is executing the functionality for some of these deer.
Well that's where we can use parallel loop.
So let me go ahead and delete these guys.
And then let me disable the script in my serial deer.
And let's go over to my parallel, dear.
So remember, the only difference between these two is that one is executing the code in parallel.
While this guy is executing the code in serial, it's the exact same code.
So with this guy, let's go ahead and enable his script and let's make 72 parallel dears.
So we'll duplicate here.
We got two and we got four and we got six.
Here is 12.
And then here is our 48.
And then here's our 72.
So now we have 72 parallel dears.
And let's see how the performance compares.
Executing the same code in parallel.
If you notice my game is no longer lagging.
It looks very smooth.
And that's because if we open up our micro profiler, look at the frame time, we are no longer averaging
50 milliseconds of frame time.
But now we are back to the nice frame time of around 16 milliseconds.
Very very cool.
And if we go ahead and pause and then let's go ahead and stop the game.
What do you see?
Well, what I see is that every single time task for each one of these dears has been split up across
all of the different worker threads.
So this guy is executing some dear functionality.
This guy is executing some dear functionality.
So as this guy and this guy, each one of our worker threads are being equally distributed code to execute
in parallel.
And that way we have now decreased the total frame time back to the average of around 16 milliseconds.
So just by converting our script from serial to parallel, we were able to save our game from this performance
degradation.
Okay, so the last thing I want to show you in this video is how you can have actors communicate with
other actors.
So let me re-enable those two local scripts that I had and replicated first, and we'll call this first
actor our actor number one.
And we'll call this second actor our actor number two.
How do we communicate between these two actors?
Well, let's go ahead and grab each one of the respective actors for each script.
So to get the actor for this local script, we'll make a variable called actor.
And let's simply going to be equal to script.
And our script has a function called get actor says returns the actor associated with our script, which
is going to be actor one for our local script one, and it's going to be actor two for Local Script
two.
So let's do that for both of our local scripts and then inside of Local Script one.
Let's go ahead and also grab the actor number two for the other script.
So that's going to be equal to script dot parent dot parent.
And let's go ahead and grab actor number two.
Now to communicate with this actor.
Let's go ahead and down here.
Let's refer to this actor.
And inside of this actor there's a function called send message.
And it says sends a message to an actor.
We give it a topic, the string of the message and then any other arguments we want to pass.
So this is where we can send information between different scripts using actors.
And that way inside of local script.
Number two, we can listen to an event inside of our actor, and I believe it's called Bind to Message.
There we go.
So we call this function.
We give it the topic and then a function to connect to that event.
And we can also connect to it in parallel as well.
So let's just connect to it normally.
And cereal the topic what should our topic be.
Let's just make our topic.
Hello.
We'll make the topic hello.
And then we'll also connect to this hello topic.
And we'll put a function here.
And then we'll get any other information that is passed when another actor says hello to us.
So when we call the send message function for this particular topic, let's give some information like
one, two and three.
And then inside of local script number two we can go ahead and print out the other script.
Said hi to us.
So hello.
And then we could do something like printing out all of those other arguments into the console.
Now for Local Script one.
Let's actually go ahead and have a while loop down here, and let's send a message to our other actor
every single second.
So we'll do task dot wait one and then we'll send a message.
And the reason I'm putting my task dot wait right here is because I don't know which one of these scripts
is going to compile and execute first.
So this script might execute and compile first.
So that means when it goes to send a message, that first message is going to be lost.
Because our second local script hasn't binded to that particular topic yet.
But now that we have binded to this hello topic, and now we're sending a message with this topic of
hello and some numbers, we should see the numbers one, two, three print out inside of the console.
So let's go ahead and play test the game and there we go.
Hello 123.
And every single second goes by we are being sent a message through the actors.
Very cool.
Now if you want to be able to communicate between different scripts underneath actors using bindable
events, then you can do so as well because the fire method for the bind.
Event is read and write safe, so you can call this function while you're executing in parallel and
you won't run into any errors.
All right.
So that was a lot of information we just learned.
So your final question may be when should I use parallel Liu.
And what should I use it on.
Well you should use parallel Liu when you need to perform a lot of heavy calculations that will eat
up your frame time if it was executed in serial.
So, for example, we showed you how these dears.
My serial deer ate up my frame time.
But by splitting it into multiple different actors and executing it on multiple different threads,
I was able to save the performance of my game.
Same thing goes with my local scripts that were doing a bunch of garbage and causing the frame time
to suffer.
If I split those up between different actors, I was able to improve the frame time.
Now, things that perform a lot of heavy calculations can be all types of things, such as some kind
of raycasting system or a terrain generation system.
Or maybe you have a bunch of pathfinding AI in your game, and in fact, parallel Liu can be great for
pathfinding systems when you have dozens or maybe even hundreds of AI.
Any time you need to do a bunch of expensive calculations that are going to cause your frame times to
skyrocket.
Yeah, you should try separating it into multiple different threads.
If you don't have any performance problems currently in your projects, or you don't have any crazy
math calculations and everything in your game is working fine on serial, then great, you don't need
to convert any of it into parallel.
Only when you notice that you can improve the performance of your games by executing code in parallel,
should you use it?
Otherwise, that's all for me for this lecture.
I hope you now have a better understanding of parallel Liu, as the documentation can be quite confusing
and there are not many good resources out there to teach parallel.
Liu.
Thank you for watching.